package fgin

import (
	"context"
	"errors"
	"fmt"
	"math/rand"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/hashicorp/consul/api"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/connectivity"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/status"
)

type FgrpcClientConn struct {
	*grpc.ClientConn
}

func (f *FgrpcClientConn) Close() {
	// 重写原生的close,防止用户将链接关闭后，无法调用grpc服务
	logSuger.Warn("fgin框架中,默认使用grpc长链接调用,无需关闭链接")
}

// 重新grpc conn出现断开后，可以创建一个新的
type RestartFgrpcClient struct {
	ServiceName  string   // grpc服务名
	Tag          string   // grpc服务tag
	Address      string   // grpc直连地址 当serviceName和tag存在的时候，可以不填
	isLock       chan int // 锁
	timeInterval int      // 长期间隔时间 默认3s
	lastTime     time.Time
}

var (
	fgrpcClientConn    = make(map[string]*FgrpcClientConn)
	fgrpcRestartClient = make(map[string]*RestartFgrpcClient)
)

// Deprecated: 该方法用于需要关闭conn的时候使用。已废弃，请使用兼容性更强的GetGrpcClientConnV2方法
func GetGrpcClientConn(serviceName, tag string) (*grpc.ClientConn, error) {
	if fginConfig.Consul == (Consul{}) {
		panic("要使用grpc客户端,必须配置服务中心")
	}
	// consul
	consulConfig := api.DefaultConfig()
	consulConfig.Address = fginConfig.Consul.Address
	consulConfig.Token = fginConfig.Consul.Token
	consulClient, err := api.NewClient(consulConfig)
	if err != nil {
		errInfo := fmt.Sprintf("获取consul失败, err:%v", err)
		return nil, errors.New(errInfo)
	}
	services, _, err := consulClient.Health().Service(serviceName, tag, true, nil)
	if err != nil {
		errInfo := fmt.Sprintf("grpc服务,service_name:%v,tag:%v不存在", serviceName, tag)
		return nil, errors.New(errInfo)
	}
	//获取链接
	addr := services[0].Service.Address + ":" + strconv.Itoa(services[0].Service.Port)
	//-------------------grpc-----------------
	clientConn, err := newDial(addr)
	if err != nil {
		errInfo := fmt.Sprintf("grpc.Dial:%v", err)
		return nil, errors.New(errInfo)
	}
	return clientConn, nil
}

// 优化方法兼容直接给grpc服务直连，优先从consul去拿
func initGrpcClientV2() {
	if len(fginConfig.GrpcClient) < 1 {
		// 未配置客户端
		return
	}
	// 是否配置consul
	isHasConsul := true
	if fginConfig.Consul == (Consul{}) {
		// 没有配置consul
		isHasConsul = false
	}
	// consul
	var (
		consulClient *api.Client
		err          error
	)
	if isHasConsul {
		consulConfig := api.DefaultConfig()
		consulConfig.Address = fginConfig.Consul.Address
		consulConfig.Token = fginConfig.Consul.Token
		consulClient, err = api.NewClient(consulConfig)
		if err != nil {
			panic(fmt.Sprintf("api.NewClient:%v", err))
		}
	}
	for _, v := range fginConfig.GrpcClient {
		if v.ServiceName == "" {
			panic("grpc_client配置,service_name不能存在空")
		}
		addr := ""
		// 优先consul
		if isHasConsul {
			services, _, err := consulClient.Health().Service(v.ServiceName, v.Tag, true, nil)
			if err != nil {
				panic(fmt.Sprintf("已配置consul,grpc服务,service_name:%v,tag:%v不存在", v.ServiceName, v.Tag))
			}
			//获取链接
			if len(services) != 0 {
				addr = weightedRandomBinary(services)
			}
		}
		// consul没有取到再直连
		if addr == "" {
			if v.Address == "" {
				panic(fmt.Sprintf("consul取不到grpc链接,grpc服务,service_name:%v,address:%v不存在", v.ServiceName, v.Address))
			}
			addr = v.Address
		}
		//-------------------grpc-----------------
		clientConn, err := newDial(addr)
		if err != nil {
			panic(fmt.Sprintf("serviceName:%v, grpc.Dial:%v", v.ServiceName, err))

		}
		fgrpcClientConn[v.ServiceName] = &FgrpcClientConn{
			ClientConn: clientConn,
		}
		// 初始化grpc重连对象
		fgrpcRestartClientItem := &RestartFgrpcClient{
			ServiceName:  v.ServiceName,
			Tag:          v.Tag,
			Address:      v.Address,
			isLock:       make(chan int, 1),
			timeInterval: 3,
			lastTime:     time.Now(),
		}
		fgrpcRestartClientItem.isLock <- 1
		fgrpcRestartClient[v.ServiceName] = fgrpcRestartClientItem
		// 打印连接到grpc服务日志
		logSuger.Info(fmt.Sprintf("连接grpc服务 name: %s, tag: %s, addr: %s", v.ServiceName, v.Tag, addr))
	}
	// 启动每间隔10s进行一次grpc链接检测
	go watchConnection()
}

// 该方法用于需要关闭conn的时候使用，优化方法兼容直接给grpc服务直连
func GetGrpcClientConnV2(serviceName, tag, address string) (*grpc.ClientConn, error) {
	// 是否配置consul
	isHasConsul := true
	if fginConfig.Consul == (Consul{}) {
		isHasConsul = false
	}
	// consul
	var (
		consulClient *api.Client
		err          error
		addr         string
	)

	if isHasConsul {
		// 配置了consul
		consulConfig := api.DefaultConfig()
		consulConfig.Address = fginConfig.Consul.Address
		consulConfig.Token = fginConfig.Consul.Token

		consulClient, err = api.NewClient(consulConfig)
		if err != nil {
			errInfo := fmt.Sprintf("获取consul失败, err:%v", err)
			return nil, errors.New(errInfo)
		}
		services, _, err := consulClient.Health().Service(serviceName, tag, true, nil)
		if err != nil {
			errInfo := fmt.Sprintf("grpc服务,service_name:%v,tag:%v不存在", serviceName, tag)
			return nil, errors.New(errInfo)
		}
		//获取链接
		if len(services) != 0 {
			addr = weightedRandomBinary(services)
		}
	}
	if addr == "" {
		if address == "" {
			return nil, errors.New("consul取不到grpc链接，address也为空")
		}
		addr = address
	}
	//-------------------grpc-----------------
	clientConn, err := newDial(addr)
	if err != nil {
		errInfo := fmt.Sprintf("grpc.Dial:%v", err)
		return nil, errors.New(errInfo)
	}
	// 打印连接到grpc服务日志
	logSuger.Info(fmt.Sprintf("连接grpc服务 name: %s, tag: %s, addr: %s", serviceName, tag, addr))
	return clientConn, nil
}

// 自定义dial
func newDial(addr string) (*grpc.ClientConn, error) {
	// 2025-04-01 将opts配置抛出，实现更多框架自由度
	// opts := []grpc.DialOption{}
	// opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(fgrpcClientInterceptor()))
	// 使用可注册的opts
	opts := grpcDialOptionsObj.opts
	// 默认安全协议
	if !grpcDialOptionsObj.disableDefaultWithTransportCredentials {
		opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
	}
	// 默认一元拦截器
	if !grpcDialOptionsObj.disableDefaultWithUnaryInterceptor {
		opts = append(opts, grpc.WithUnaryInterceptor(fgrpcClientInterceptor()))
	}
	clientConn, err := grpc.Dial(addr, opts...)
	return clientConn, err
}

// grpc client拦截器
func fgrpcClientInterceptor() grpc.UnaryClientInterceptor {
	return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		// 断言context
		if v, ok := ctx.(*gin.Context); ok {
			// gin 类型的context需独立提取
			ctx = v.Request.Context()
		}
		// jaeger将span处理
		ctx = GetGrpcCtx(ctx)

		// 处理超时
		if fginConfig.Timeout > 0 {
			t, ok := ctx.Deadline()
			var (
				cancel context.CancelFunc
			)
			remainMs := fginConfig.Timeout
			if ok && !fginConfig.DisableRequestTimeout {
				// 继承上游服务的耗时，和配置中的对比，优先小值
				remainMs = min(fginConfig.Timeout, int(time.Until(t).Milliseconds()))
			}
			ctx, cancel = context.WithTimeout(ctx, time.Duration(remainMs)*time.Millisecond)
			defer cancel()
		}
		var err error
		// 熔断器
		if err = fgrpcClientCircuitMiddleware(method); err != nil {
			return err
		}

		// grpc client 自定义拦截器
		ctx, err = PlugsGrpcClientMiddStart(ctx, method, req, reply)
		if err != nil {
			return err
		}
		err = invoker(ctx, method, req, reply, cc, opts...)
		// grpc client 自定义拦截器 执行后
		if err == nil {
			err = PlugsGrpcClientMiddAfterStart(ctx, method, req, reply, err)
		}
		if err != nil {
			// 熔断器标识失败
			circuitBreaker.MarkFailed()
			fgrpcClientAutoRestart(err)
		} else {
			// 熔断器标识成功
			circuitBreaker.MarkSuccess()
		}
		return err
	}
}

// 熔断中间件
func fgrpcClientCircuitMiddleware(method string) error {
	if err := circuitBreaker.Allow(); err != nil {
		logSuger.Info(fginConfig.ServiceName + ":" + method + circuitErrMsg)
		return status.Error(circuitCode, fginConfig.ServiceName+":"+method+circuitErrMsg)
	}
	return nil
}

// 自动重重新创建conn中间件
func fgrpcClientAutoRestart(grpcErr error) {
	err, ok := status.FromError(grpcErr)
	if !ok {
		// 非grpc格式报错, 不进行更新链接处理
		return
	}
	for serviceName, fgrpcClient := range fgrpcClientConn {
		switch err.Code() {
		case codes.Unavailable:
			// 找不到服务，可能瞬时的，可以重试，或者创建新链接
			if fgrpcClient.ClientConn.GetState() != connectivity.Ready {
				logSuger.Warn(fmt.Sprintf("fgrpcClientAutoRestart grpc is no ready, serviceName is %s", serviceName))
				restartObj, _ := GetRestartGrpcObj(serviceName)
				restartObj.RestartFgrpcClientConn()
			}
		case codes.Canceled:
			// 服务端取消操作,可能是服务端conn已关闭
			if fgrpcClient.ClientConn.GetState() != connectivity.Ready {
				logSuger.Warn(fmt.Sprintf("fgrpcClientAutoRestart grpc is no ready, serviceName is %s", serviceName))
				restartObj, _ := GetRestartGrpcObj(serviceName)
				restartObj.RestartFgrpcClientConn()
			}
			restartObj, _ := GetRestartGrpcObj(serviceName)
			restartObj.RestartFgrpcClientConn()
		default:
			continue
		}
	}
}

// 重启grpc连接方法
func (r *RestartFgrpcClient) RestartFgrpcClientConn() (*FgrpcClientConn, error) {
	select {
	case <-r.isLock:
		if int(time.Since(r.lastTime).Seconds()) < r.timeInterval {
			r.isLock <- 1
			return nil, fmt.Errorf("serviceName:%s,在冷却期", r.ServiceName)
		}

		var mu sync.Mutex
		mu.Lock()
		v, ok := fgrpcClientConn[r.ServiceName]
		if !ok {
			errInfo := fmt.Sprintf("没有%v连接服务", r.ServiceName)
			return nil, errors.New(errInfo)
		}
		defer func() {
			r.isLock <- 1
			r.lastTime = time.Now()
			mu.Unlock()
		}()
		newConn, err := GetGrpcClientConnV2(r.ServiceName, r.Tag, r.Address)
		if err != nil {
			errInfo := fmt.Sprintf("创建grpc conn失败: %v", r.ServiceName)
			return nil, errors.New(errInfo)
		}
		/*
			newFgrpcConn := &FgrpcClientConn{
				ClientConn: newConn,
			}
			fgrpcClientConn[r.ServiceName] = newFgrpcConn
			// 成功创建出新的conn才进行关闭原来的
			defer v.ClientConn.Close()
		*/
		// 关闭原grpc链接
		v.ClientConn.Close()
		// 覆盖新链接进入
		v.ClientConn = newConn
		return v, nil
	default:
		return nil, fmt.Errorf("serviceName:%s,未抢到锁", r.ServiceName)
	}
}

// 获取grpc链接重启对象
func GetRestartGrpcObj(serviceName string) (*RestartFgrpcClient, error) {
	v, ok := fgrpcRestartClient[serviceName]
	if !ok {
		return nil, fmt.Errorf("未找到%s重启对象", serviceName)
	}
	return v, nil
}

// 监听grpc conn状态 间隔10s进行一次
func watchConnection() {
	for {
		time.Sleep(10 * time.Second)
		for serviceName, fgrpcClient := range fgrpcClientConn {
			// 循环检测grpc conn是否就绪，如果未就绪则进行重连
			if fgrpcClient.ClientConn.GetState() != connectivity.Ready {
				logSuger.Warn(fmt.Sprintf("watchConnection is no ready, serviceName is %s", serviceName))
				restartObj, _ := GetRestartGrpcObj(serviceName)
				restartObj.RestartFgrpcClientConn()
			}
		}
	}
}

// 加权随机 负载均衡获取链接
func weightedRandomBinary(services []*api.ServiceEntry) string {
	weights, servicePaths := []int{}, []string{}
	for _, service := range services {
		for _, tag := range service.Service.Tags {
			tagSplit := strings.Split(tag, "|")
			if tagSplit[0] == sSERVICEWEIGHTPRIX {
				weight, err := strconv.Atoi(tagSplit[1])
				if err != nil {
					break
				}
				// 加入权重
				weights = append(weights, weight)
				// 加入服务链接
				servicePath := service.Service.Address + ":" + strconv.Itoa(service.Service.Port)
				servicePaths = append(servicePaths, servicePath)
				break
			}
		}
	}
	// 增加保底，获取权重发生失败
	if len(servicePaths) == 0 {
		return services[0].Service.Address + ":" + strconv.Itoa(services[0].Service.Port)
	}

	// 加权随机算法
	randObj := rand.New(rand.NewSource(time.Now().UnixNano()))
	sum := 0
	sumWeights := make([]int, len(weights))
	// 生成累加权重数组
	for i, w := range weights {
		sum += w
		sumWeights[i] = sum
	}
	// 随机数范围 [0, sum)
	r := randObj.Intn(sum)
	// 二分查找命中位置
	low, high := 0, len(sumWeights)
	for low < high {
		mid := low + (high-low)/2
		if sumWeights[mid] <= r {
			low = mid + 1
		} else {
			high = mid
		}
	}
	return servicePaths[low]
}
